﻿using System;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using Kawanon.QtHoney;
using System.Windows.Forms;
using Kawanon.Foundation;
using System.Reflection;
using System.Globalization;
using System.ComponentModel;

namespace Kawanon.Localization
{
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum)]
	public class ResourceKeyAttribute : Attribute
	{
		public string Key { get; private set; }

		public ResourceKeyAttribute(string key)
		{
			Key = key;
		}

		public static string GetKey(Enum e)
		{
			ResourceKeyAttribute attr = (ResourceKeyAttribute)
				Attribute.GetCustomAttribute(e.GetType(), typeof(ResourceKeyAttribute));
			if (attr == null)
			{
				return null;
			}
			return attr.Key;
		}
	}

	public class LocalizedDescriptionAttribute : DescriptionAttribute
	{
		public LocalizedDescriptionAttribute(string key)
			: base(StringResource.Localize(MakeFullKey(key), false))
		{
		}

		public LocalizedDescriptionAttribute(string key, int index)
			: base(StringResource.Localize(MakeFullKey(key), index, false))
		{
		}

		public LocalizedDescriptionAttribute(Type t)
			: this(t.Name)
		{
		}

		public LocalizedDescriptionAttribute(Type t, int index)
			: this(t.Name, index)
		{
		}

		public LocalizedDescriptionAttribute(object enumObj)
			: base(StringResource.Localize((Enum)enumObj))
		{
		}

		private static string MakeFullKey(string key)
		{
			return key + "_Description";
		}
	}

	public class LocalizedDisplayNameAttribute : DisplayNameAttribute
	{
		public LocalizedDisplayNameAttribute(string key)
			: base(StringResource.Localize(MakeFullKey(key), false))
		{
		}

		public LocalizedDisplayNameAttribute(string key, int index)
			: base(StringResource.Localize(MakeFullKey(key), index, false))
		{
		}

		public LocalizedDisplayNameAttribute(Type t)
			: this(t.Name)
		{
		}

		public LocalizedDisplayNameAttribute(Type t, int index)
			: this(t.Name, index)
		{
		}

		public LocalizedDisplayNameAttribute(object enumObj)
			: base(StringResource.Localize((Enum)enumObj))
		{
		}

		private static string MakeFullKey(string key)
		{
			return key + "_DisplayName";
		}
	}

	public class LocalizedCategoryAttribute : CategoryAttribute
	{
		public LocalizedCategoryAttribute(string key)
			: base(StringResource.Localize(MakeFullKey(key), false))
		{
		}

		public LocalizedCategoryAttribute(string key, int index)
			: base(StringResource.Localize(MakeFullKey(key), index, false))
		{
		}

		public LocalizedCategoryAttribute(Type t)
			: this(t.Name)
		{
		}

		public LocalizedCategoryAttribute(Type t, int index)
			: this(t.Name, index)
		{
		}

		public LocalizedCategoryAttribute(object enumObj)
			: base(StringResource.Localize((Enum)enumObj))
		{
		}

		protected override string GetLocalizedString(string value)
		{
			//NOTE: 例えば「Behavior」が「動作」に翻訳されてしまう
			//string str = base.GetLocalizedString(value);
			return value;
		}

		private static string MakeFullKey(string key)
		{
			return key + "_Category";
		}
	}

	public class LocalizedEnumDisplayNameAttribute : EnumDisplayNameAttribute
	{
		private int m_index;
		private Enum m_cachedValue = null;

		public int Index { get { return m_index; } }

		public LocalizedEnumDisplayNameAttribute(string key)
			: base(StringResource.Localize(key, false))
		{
		}

		public LocalizedEnumDisplayNameAttribute(string key, int index)
			: base(StringResource.Localize(key, index, false))
		{
		}

		public LocalizedEnumDisplayNameAttribute()
		{
			// 列挙値をインデックスとする
			m_index = -1;
		}

		public LocalizedEnumDisplayNameAttribute(int index)
		{
			m_index = index;
		}

		public override string GetDisplayName(object value, CultureInfo culture)
		{
			if (value != m_cachedValue)
			{
				int index = m_index;
				if (index == -1)
				{
					index = Convert.ToInt32(value);
				}
				string typeName = value.GetType().Name;
				base.m_name = StringResource.Localize(typeName + "_DisplayName", index, false);
				m_cachedValue = (Enum)value;
			}
			return base.m_name;
		}
	}

	public class StringResource
	{
		private const string InternalPathPrefix = "res:///";

		public class Entry
		{
			public string Key { get; private set; }
			public StringResource Owner { get; private set; }

			internal Entry(StringResource owner, string key)
			{
				Owner = owner;
				Key = key;
			}

			public bool Exists { get { return Owner.m_dic.ContainsKey(Key); } }

			public string Raw
			{
				get
				{
					if (Exists)
					{
						return Owner.m_dic[Key];
					}
					else
					{
						return null;
					}
				}
			}

			public string GetText(bool nullOnError)
			{
				return StringResource.Swap(Owner,
					() => StringResource.Localize(Key, nullOnError));
			}

			public string Text
			{
				get
				{
					return GetText(false);
				}
			}

			public string At(int index, bool nullOnError)
			{
				return StringResource.Swap(Owner,
					() => StringResource.Localize(Key, index, nullOnError));
			}

			public string this[int index]
			{
				get
				{
					return At(index, false);
				}
			}
		}

		private static StringResource s_default = null;
		// 静的初期化時以外は変更されないので、m_cacheのアクセス以外は不要？
		private static object s_sync = new object();

		static StringResource()
		{
			Refresh();
		}

		public static bool Available { get { return s_default != null; } }

		public static StringResource Default { get { return s_default; } }

		internal Dictionary<string, string> m_dic = new Dictionary<string, string>();
		internal Dictionary<string, IList<string>> m_cache = new Dictionary<string, IList<string>>();

		private StringResource(Stream stm)
		{
			try
			{
				Init(stm);
			}
			catch (Exception ex)
			{
				Utility.ForException(ex,
					"An error has been occurred in the language resource");
			}
		}

		private void Init(Stream stm)
		{
			XmlTextReader reader = new XmlTextReader(stm);
			try
			{
				while (reader.Read())
				{
					if (reader.NodeType != XmlNodeType.Element)
					{
						continue;
					}
					if (reader.Name == "root")
					{
						continue;
					}
					m_dic.Add(reader.Name, reader.ReadString());
				}
			}
			finally
			{
				reader.Close();
			}
		}

		public static string ToInternalPath(string resName)
		{
			return InternalPathPrefix + resName;
		}

		public static string ToResourceName(string path)
		{
			if (!path.StartsWith(InternalPathPrefix))
			{
				return null;
			}

			return path.Substring(InternalPathPrefix.Length);
		}

		public static bool Exists(string path)
		{
			string resName = ToResourceName(path);
			if (resName != null)
			{
				Assembly asm = Assembly.GetExecutingAssembly();
				string[] names = asm.GetManifestResourceNames();
				int index = Array.IndexOf(names, resName);
				return (index != -1);
			}
			else
			{
				return File.Exists(path);
			}
		}

		public static StringResource From(Stream stm)
		{
			return new StringResource(stm);
		}

		public static StringResource From(string path)
		{
			string resName = ToResourceName(path);
			if (resName != null)
			{
				Assembly asm = Assembly.GetExecutingAssembly();
				Stream stm = asm.GetManifestResourceStream(resName);
				return From(stm);
			}
			else
			{
				return From(File.Open(path, FileMode.Open));
			}
		}

		public static T Swap<T>(StringResource res, Func<T> fun)
		{
			StringResource def = StringResource.s_default;
			StringResource.s_default = res;
			try
			{
				return fun();
			}
			finally
			{
				StringResource.s_default = def;
			}
		}

		// 属性に対してすぐに反映されないので利用不可
		public static void Refresh()
		{
			string path = Plugins.GetLanguageResourcePath();
			lock (s_sync)
			{
				s_default = From(path);
			}
		}

		public Entry this[string key]
		{
			get
			{
				return new Entry(this, key);
			}
		}

		public static string Localize(string key, bool nullOnError)
		{
			if (!Available)
			{
				return nullOnError ? null : key;
			}
			string value;
			lock (s_sync)
			{
				if (!s_default.m_dic.TryGetValue(key, out value))
				{
					return nullOnError ? null : key;
				}
			}
			return value.Trim();
		}

		public static string Localize(string key)
		{
			return Localize(key, false);
		}

		public static string Localize(string key, string defaultValue)
		{
			string str = StringResource.Localize(key, true);
			if (str == null)
			{
				return defaultValue;
			}
			return str;
		}

		// QTTabBar 本体のように一行づつに分解
		private static string[] SplitRaw(string key)
		{
			string str = Localize(key, true);
			if (str == null)
			{
				return null;
			}
			string[] a = str.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
			string[] ret = Array.ConvertAll(a,
				(s) => s.Replace("\\r\\n", "\r\n").Trim());
			return ret;
		}

		public static IList<string> Split(string key)
		{
			if (!Available)
			{
				return null;
			}
			IList<string> values;
			lock (s_sync)
			{
				if (s_default.m_cache.TryGetValue(key, out values))
				{
					return values;
				}

				string[] a = SplitRaw(key);
				if (a != null)
				{
					values = Array.AsReadOnly(a);
					s_default.m_cache[key] = values;
					return values;
				}
			}
			return null;
		}

		public static IList<string> Split(string key, int required, string padding)
		{
			IList<string> list = Split(key);
			List<string> a = new List<string>();
			if (list != null)
			{
				a.AddRange(list);
			}
			for (int i = a.Count; i < required; i++)
			{
				a.Add(padding);
			}
			return a;
		}

		public static string Localize(string key, int index, bool nullOnError)
		{
			IList<string> values = StringResource.Split(key);
			if (values == null || values.Count <= index)
			{
				if (nullOnError)
				{
					return null;
				}
				else
				{
					return string.Format("{0} ({1})", key, index);
				}
			}
			return values[index];
		}

		public static string Localize(string key, int index)
		{
			return Localize(key, index, false);
		}

		public static string Localize(Enum e, bool nullOnError)
		{
			string key = ResourceKeyAttribute.GetKey(e);
			if (key == null)
			{
				throw new Exception("ResourceKey not found");
			}

			int index = Convert.ToInt32(e);
			return Localize(key, index, nullOnError);
		}

		public static string Localize(Enum e, string defaultValue)
		{
			string str = StringResource.Localize(e, true);
			if (str == null)
			{
				return defaultValue;
			}
			return str;
		}

		public static string Localize(Enum e)
		{
			return Localize(e, false);
		}

		private static Action<T> CreateLocalizer<T>(string commonKey, string[] properties)
		{
			string[] keys = Array.ConvertAll(properties, (s) => commonKey + "_" + s);
			IList<string>[] values = Array.ConvertAll(keys, (key) => StringResource.Split(key));

			int index = 0;
			return delegate(T obj)
			{
				for (int i = 0; i < properties.Length; i++)
				{
					IList<string> value = values[i];
					if (value == null || value.Count <= index)
					{
						continue;
					}
					Reflection.SetProperty(obj, properties[i], value[index]);
				}
				index++;
			};
		}

		private static void Apply0(Action<ToolStripItem> fun, ToolStripItemCollection items)
		{
			foreach (ToolStripItem item in items)
			{
				if (item is ToolStripSeparator)
				{
					continue;
				}
				fun(item);

				ToolStripDropDownItem item2 = item as ToolStripDropDownItem;
				if (item2 != null)
				{
					Apply0(fun, item2.DropDownItems);
				}
			}
		}

		public static void Apply(string commonKey, ToolStripItemCollection items)
		{
			Action<ToolStripItem> fun = CreateLocalizer<ToolStripItem>(commonKey,
				new string[] { "Text", "ToolTipText" });
			Apply0(fun, items);
		}

		public static void Apply(string key, params Control[] controls)
		{
			for (int i = 0; i < controls.Length; i++)
			{
				controls[i].Text = Localize(key, i, false);
			}
		}

		public static void Apply(MessageBoxButtons buttons, params Control[] controls)
		{
			IList<string> values = Split("DialogButton_Resource", 2, string.Empty);
			IList<string> yesNo = Split("YesNo_Resource", 2, string.Empty);

			switch (buttons)
			{
				case MessageBoxButtons.OK:
					controls[0].Text = values[0];
					break;
				case MessageBoxButtons.OKCancel:
					controls[0].Text = values[0];
					controls[1].Text = values[1];
					break;
				case MessageBoxButtons.YesNo:
					controls[0].Text = yesNo[0];
					controls[1].Text = yesNo[1];
					break;
			}
		}

		public static void Apply(string key, ListView.ColumnHeaderCollection columns)
		{
			for(int i = 0; i < columns.Count; i++)
			{
				columns[i].Text = Localize(key, i, false);
			}
		}

		public static void Apply(string key, ListViewGroupCollection groups)
		{
			for (int i = 0; i < groups.Count; i++)
			{
				groups[i].Header = Localize(key, i, false);
			}
		}

		public static void Apply(string commonKey, ListView.ListViewItemCollection items)
		{
			Action<ListViewItem> fun = CreateLocalizer<ListViewItem>(commonKey,
				new string[] { "Text", "ToolTipText" });
			for (int i = 0; i < items.Count; i++)
			{
				fun(items[i]);
			}
		}

		public static string CombineKey(Type t, string key)
		{
			return t.Name + key;
		}
	}
}
